﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Management.Automation;
using BGShell.Components;
using BGShell.PowerShell.LanguageService.Threading;
using BGShell.PowerShell.Runspaces;

namespace BGShell.PowerShell.LanguageService
{
    partial class PSLanguageService
    {
        private sealed class TokensAndErrors
        {
            public IList<PSToken> Tokens = new List<PSToken>();
            public IList<PSParseError> Errors = new List<PSParseError>();

            public void Add(PSToken token)
            {
                Tokens.Add(token);
            }

            public void Add(PSParseError error)
            {
                Errors.Add(error);
            }
        }

        private sealed class HighlighterJob : BackgroundJob
        {
            private readonly PSLanguageService _langSvc;
            private readonly String _text;

            private volatile Boolean _cancelled;

            private Collection<PSToken> _tokens;
            private Collection<PSParseError> _errors;

            public HighlighterJob(PSLanguageService langSvc, String text)
            {
                _text = text;
                _langSvc = langSvc;
            }

            internal override void Execute()
            {
                if (_tokens == null)
                {
                    _tokens = PSParser.Tokenize(_text, out _errors);
                }

                UpdateTokens();
            }

            internal void Cancel()
            {
                _cancelled = true;
            }

            internal bool HasTokensRemaining
            {
                get { return (_tokens == null) || (_tokens.Count > 0); }
            }

            private void UpdateTokens()
            {
                var view = _langSvc._viewport;

                var line = new TokensAndErrors();
                var viewport = new TokensAndErrors();
                var remaining = new TokensAndErrors();

                foreach (var token in _tokens)
                {
                    if (view.CurrentLineContains(token))
                    {
                        line.Add(token);
                    }
                    else if (view.Contains(token))
                    {
                        viewport.Add(token);
                    }
                    else
                    {
                        remaining.Add(token);
                    }
                }

                foreach (var error in _errors)
                {
                    if (view.CurrentLineContains(error.Token))
                    {
                        line.Add(error);
                    }
                    else if (view.Contains(error.Token))
                    {
                        viewport.Add(error);
                    }
                    else
                    {
                        remaining.Add(error);
                    }
                }

                UpdateTokens(line);
                UpdateTokens(viewport);

                _tokens = new Collection<PSToken>(remaining.Tokens);
                _errors = new Collection<PSParseError>(remaining.Errors);
            }

            private TextRange GetTextRange(PSToken token)
            {
                var start = _langSvc.Reader.GetRealCharIndex(token.Start);
                var end = _langSvc.Reader.GetRealCharIndex(token.Start + token.Length);

                return new TextRange(start, end);
            }

            private DataTipInfo GetTipInfo(PSToken token)
            {
                var type = DataTipType.None;

                switch (token.Type)
                {
                    case PSTokenType.Variable:
                        type = DataTipType.Variable;
                        break;

                    case PSTokenType.Command:
                        type = DataTipType.Command;
                        break;

                    case PSTokenType.Type:
                        type = DataTipType.Type;
                        break;
                }

                return new DataTipInfo(GetTextRange(token), token.Content, type);
            }

            private void UpdateTokens(TokensAndErrors tae)
            {
                _langSvc.BeginUpdate();

                try
                {
                    foreach (var token in tae.Tokens)
                    {
                        DataTipInfo dataTipInfo = GetTipInfo(token);
                        DataTipItem dataTip = null;

                        if (dataTipInfo.Type != DataTipType.None)
                        {
                            dataTip = _langSvc.RequestTip(dataTipInfo);
                            dataTipInfo = new DataTipInfo(dataTipInfo, dataTip);
                        }

                        _langSvc.HighlightRange(dataTipInfo, token.Type);

                        if (_cancelled)
                        {
                            return;
                        }
                    }

                    foreach (var error in tae.Errors)
                    {
                        _langSvc.HighlightError(GetTextRange(error.Token), error.Message);

                        if (_cancelled)
                        {
                            return;
                        }
                    }
                }
                finally
                {
                    _langSvc.EndUpdate();
                }
            }
        }
    }
}
